import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.Vector;

import IA.Energia.Central;
import IA.Energia.Centrales;
import IA.Energia.Cliente;
import IA.Energia.Clientes;


public class Estado {
	static int tipoSolucion = 0;
	
	
	HashMap<Cliente, Central> clientes;
	HashMap<Central, CentralConClientes> centrales;
	double gananciaClientes = 0;
	double costeClientes = 0;
	double costeCentrales = 0;
	double penalizacion = 0;
	double incPenalizacion = 0;
	
	public Estado() {
		
	}
	
	public Estado(Estado orig) {
		costeCentrales = orig.calcularCosteCentrales();
		costeClientes = orig.calcularCosteClientes();
		gananciaClientes = orig.calcularGananciaClientes();
		this.set_centrales(orig.get_centrales());
		this.set_clientes(orig.get_clientes());
	}
	public Estado(Clientes clientes, Centrales centrales) {
		init(clientes,centrales,0);
	}

	public Estado(Clientes clientes, Centrales centrales, int tipoSolucion){	
		init(clientes,centrales,tipoSolucion);
	}
	
	
	public void setIncPenalizacion(double inc) {
		incPenalizacion = inc;
		
	}
	
	public double getPenalizacion() {
		return penalizacion;
	}
	private void initGreedy(Clientes clientes, Centrales centrales, int tipoSolucion) {

		
		for (Central central : centrales)
			this.centrales.put(central, new CentralConClientes());
		
		for (Cliente cliente : clientes) {
			Central centralMasCercana = null;
			this.clientes.put(cliente, null);
			if (cliente.getContrato() == Cliente.GARANTIZADO) 
				for (Central central : centrales) {
					//this.centrales.put(central, new CentralConClientes());
					if (can_put(cliente, central)) {
						if(centralMasCercana == null || calcularDistancia(cliente, centralMasCercana) > calcularDistancia(cliente, central))
						centralMasCercana = central;
					}
				}
				Poner(cliente, centralMasCercana);
		}
		
	}

	private double calcularDistancia(Cliente cliente, Central central) {
		return Math.sqrt(Math.pow(cliente.getCoordX() - central.getCoordX(), 2) 
										+ Math.pow(cliente.getCoordY() - central.getCoordY(),2));
	}
	
	private void initRandom(Clientes clientes, Centrales centrales, int tipoSolucion) {
		for (Central central : centrales) {
			this.centrales.put(central, new CentralConClientes());
			costeCentrales+=costeCentral(central);
		}
		
			Vector<Central> centralesRandom = new Vector<Central>();
			for (Central central : centrales){
				this.centrales.put(central, new CentralConClientes());
				centralesRandom.add(central);
			}
			for (Cliente cliente : clientes) {
				this.clientes.put(cliente, null);
				if (cliente.getContrato() == Cliente.GARANTIZADO){
					boolean assigned = false;
					while(!assigned){
						Random random = new Random();
						int i = Math.abs(random.nextInt());
						i %= centralesRandom.size();
						Central central = centralesRandom.get(i);
						if (can_put(cliente, central)) {
							Poner(cliente, central);
							assigned = true;
						}
					}				
				}
			}	
	}

	private void init(Clientes clientes, Centrales centrales, int tipoSolucion) {
		this.clientes = new HashMap<Cliente,Central>(clientes.size());
		this.centrales = new HashMap<Central,CentralConClientes>(centrales.size());
		calculoInicialCentrales(centrales);
		switch (tipoSolucion) {
		case 0:
			initLinear(clientes,centrales,tipoSolucion);
			break;
		case 1:
			initRandom(clientes,centrales,tipoSolucion);
			break;
		case 2:
			initGreedy(clientes,centrales,tipoSolucion);
			break;
		case 3:
			initVacia(clientes,centrales);
			break;
		}
		calculoInicialClientes(clientes);
		
	}
	
	private void initLinear(Clientes clientes, Centrales centrales, int tipoSolucion) { 

		for (Cliente cliente : clientes) {
			this.clientes.put(cliente, null);
			if (cliente.getContrato() == Cliente.GARANTIZADO) 
				for (Central central : centrales) {
					//this.centrales.put(central, new CentralConClientes());
					if (can_put(cliente, central)) {
						Poner(cliente, central);
					}
				}
		}
		
	}
	
	private void initVacia(Clientes clientes, Centrales centrales) {
		calculoInicialCentrales(centrales);
		calculoInicialClientes(clientes);
	}
	
	private void calculoInicialCentrales(Centrales centrales) {
		for (Central central : centrales) {
			this.centrales.put(central, new CentralConClientes());
			costeCentrales+=costeCentral(central);
		}
	}
	
	private void calculoInicialClientes(Clientes clientes) {
		for (Cliente cliente : clientes) { 
			if (this.clientes.get(cliente)== null) {
				costeClientes += costeCliente(cliente);
				if (cliente.getContrato() == Cliente.GARANTIZADO) penalizacion += incPenalizacion;
			}
		}
	}
	
	public void Poner(Cliente cli, Central c)
	{
		boolean activaAntes = false, activaDespues = false;
		double costeAntes = 0, costeDespues = 0;
		CentralConClientes centralConClientes = centrales.get(c);
		if(clientes.get(cli) == null){
			activaAntes = activa(c);
			costeAntes = costeCentral(c);
			clientes.put(cli, c);
			costeClientes -= costeCliente(cli);
			centralConClientes.clientes.add(cli);
			centralConClientes.consumo += calcularConsumoConPerdida(cli, c); 
			gananciaClientes += gananciaCliente(cli);
			activaDespues = activa(c);
			costeDespues = costeCentral(c);
			if ((activaAntes && !activaDespues) || (!activaAntes && activaDespues)) {
				costeCentrales -= costeAntes;
				costeCentrales += costeDespues;
			}
			if (cli.getContrato() == Cliente.GARANTIZADO)
			penalizacion -= incPenalizacion;
		}
	}
	
	private double calcularConsumoConPerdida(Cliente cli, Central c) {
		double distancia = Math.sqrt(
				Math.pow(cli.getCoordX() - c.getCoordX(), 2) 
					+ Math.pow(cli.getCoordY() - c.getCoordY(),2));
				double perdida = 0.0;
				
				if (distancia <= 10) perdida = 0.0;
				else if (distancia <= 25) perdida = 0.10;
				else if (distancia <= 50) perdida = 0.20;
				else if (distancia <= 75) perdida = 0.40;
				else perdida = 0.60;
				return cli.getConsumo() + cli.getConsumo()*perdida;
	}
	
	public void Quitar(Cliente cli)
	{
		boolean activaAntes = false, activaDespues = false;
		double costeAntes = 0, costeDespues = 0;
		if(clientes.get(cli) != null){
			Central c = clientes.get(cli);
			activaAntes = activa(c);
			costeAntes = costeCentral(c);
			
			CentralConClientes centralConClientes = centrales.get(c);
			clientes.remove(cli);
			centralConClientes.clientes.remove(cli);
			activaDespues = activa(c);
			costeDespues = costeCentral(c);
			
			centralConClientes.consumo -= cli.getConsumo();
			gananciaClientes -= gananciaCliente(cli);
			costeClientes += costeCliente(cli);
			
			activaDespues = activa(c);
			costeDespues = costeCentral(c);
			if ((activaAntes && !activaDespues) || (!activaAntes && activaDespues)) {
				costeCentrales -= costeAntes;
				costeCentrales += costeDespues;
			}
			if (cli.getContrato() == Cliente.GARANTIZADO)
				penalizacion -= incPenalizacion;
		}
	}
	
	public HashMap<Cliente, Central> get_clientes () {
		return clientes;
	}
	
	public HashMap<Central,CentralConClientes> get_centrales () {
		return centrales;
	}

	public void set_clientes(HashMap<Cliente, Central> cli) {	
		clientes = new HashMap<Cliente, Central>(cli);
	}

	public void set_centrales(HashMap<Central, CentralConClientes> cent) {
		
		centrales = new HashMap<Central, CentralConClientes>();
		Set<Entry<Central, CentralConClientes>> entries = cent.entrySet();
		for (Entry<Central, CentralConClientes> entry : entries) {
			//creamos la nueva lista
			List<Cliente> listaClientes = new ArrayList<Cliente>(entry.getValue().clientes);
			centrales.put(entry.getKey(), 
					new CentralConClientes(listaClientes, entry.getValue().consumo));
		}
	}

	public boolean can_remove(Cliente key) {
		 return key.getContrato() != Cliente.GARANTIZADO;
	}

	public boolean can_put(Cliente key, Central key2) {
	    return (centrales.get(key2).consumo + calcularConsumoConPerdida(key, key2)) 
					<= key2.getProduccion();
	}

	public boolean canSwap(Cliente clienteA, Cliente clienteB) {
		boolean res = false;
		Central centralA = clientes.get(clienteA);
		Central centralB = clientes.get(clienteB);
		if (centralA != null && centralB != null) {
			double prodCA = centralA.getProduccion(); //capacidad de la central A
			double prodCB = centralB.getProduccion(); //capacidad de la central B
			double consCeA = centrales.get(centralA).consumo; //consumo actual central A
			double consCeB = centrales.get(centralB).consumo; //consumo actual central B
			double consClAA = calcularConsumoConPerdida(clienteA, centralA); //Consumo del cliente A en la central que esta
			double consClBB = calcularConsumoConPerdida(clienteB,centralB); //Consumo del cliente B en la central que esta
			double consClAB = calcularConsumoConPerdida(clienteA, centralB); //Consumo del cliente A en la central a la que va
			double consClBA = calcularConsumoConPerdida(clienteB,centralA); //Consumo del cliente B en la central a la que va
			
			return (prodCA >= consCeA + consClBA - consClAA)
					&& (prodCB >= consCeB + consClAB - consClBB);
		}
		return res;
	}
	
	public void Intercambiar(Cliente clienteA, Cliente clienteB) {
		Central centralA = clientes.get(clienteA), centralB = clientes.get(clienteB);
		Quitar(clienteA);
		Quitar(clienteB);
		Poner(clienteA, centralB);
		Poner(clienteB, centralA);
	}
	
	
	public boolean canChange(Cliente key, Central central, Central key2) {
		return can_put(key, key2);
	}

	public void Mover(Cliente key, Central central, Central key2) {
		Quitar(key);
		Poner(key, key2);
	} 
	
	private boolean activa(Central central) {
		CentralConClientes centralConCliente = centrales.get(central);
		return (!((CentralConClientes) centralConCliente)
		.clientes.isEmpty());
	}
	
	public double calcularCosteCentrales() {
		/*double coste = 0;
		Set<Entry<Central, CentralConClientes>> setCentrales = centrales.entrySet();
		
		for (Entry centralConCliente : setCentrales) {
			Central central = (Central) centralConCliente.getKey();
			boolean centralActiva = !((CentralConClientes) centralConCliente.getValue())
										.clientes.isEmpty();
			switch (central.getTipo()) {
				case Central.CENTRALA:
					if (centralActiva) {
						coste += central.getProduccion() * 5 + 2000;
					} else {
						coste += 1500;
					}
					break;
				case Central.CENTRALB:
					if (centralActiva) {
						coste += central.getProduccion() * 8 + 1000;
					} else {
						coste += 500;
					}
					break;
				case Central.CENTRALC:
					if (centralActiva) {
						coste += central.getProduccion() * 15 + 500;
					} else {
						coste += 150;
					}
					break;
			}
		}
		if (coste != costeCentrales) 
			System.out.println("coste de centrales mal calculado("+coste+") ("+costeCentrales+")");*/
		return costeCentrales;
	}
	
	private double costeCentral(Central central) {
		double coste = 0;
		CentralConClientes centralConCliente = centrales.get(central);
		boolean centralActiva = !((CentralConClientes) centralConCliente)
						.clientes.isEmpty();
		switch (central.getTipo()) {
			case Central.CENTRALA:
				if (centralActiva) {
				coste += central.getProduccion() * 5 + 2000;
				} else {
				coste += 1500;
				}
			break;
				case Central.CENTRALB:
				if (centralActiva) {
				coste += central.getProduccion() * 8 + 1000;
				} else {
				coste += 500;
				}
			break;
				case Central.CENTRALC:
				if (centralActiva) {
				coste += central.getProduccion() * 15 + 500;
				} else {
				coste += 150;
				}
			break;
		}
		return coste;
	}
	public double calcularCosteClientes() {
		/*double coste = 0;
		Set<Entry<Cliente, Central>> setClientes = clientes.entrySet();
		
		for (Entry<Cliente,Central> cliente : setClientes) {
			if (cliente.getValue() == null) {
				coste += cliente.getKey().getConsumo() * 5;
			}
		}
		if (coste != costeClientes) System.out.println("coste de clientes mal calculado("+coste+") ("+costeClientes+")");
		*/
		return costeClientes;
	}
	
	private double costeCliente(Cliente cliente) {
		return clientes.get(cliente) == null ? cliente.getConsumo() * 5 : 0;
	}
	
	public double calcularGananciaClientes() {
		
		/*double ganancia = 0;
		
		Set<Entry<Cliente, Central>> setClientes = clientes.entrySet();
		
		for (Entry<Cliente,Central> entry : setClientes) {
			Cliente cliente = entry.getKey();
			Central central = entry.getValue();
			if (central != null) {
				switch (cliente.getTipo()) {
					case Cliente.CLIENTEG:
						if (cliente.getContrato() == Cliente.GARANTIZADO) 
							ganancia += cliente.getConsumo() * 60;
						else 
							ganancia += cliente.getConsumo() * 50;
						break;
					case Cliente.CLIENTEMG:
						if (cliente.getContrato() == Cliente.GARANTIZADO) 
							ganancia += cliente.getConsumo() * 50;
						else 
							ganancia += cliente.getConsumo() * 40;
						break;
					case Cliente.CLIENTEXG:
						if (cliente.getContrato() == Cliente.GARANTIZADO) 
							ganancia += cliente.getConsumo() * 40;
						else 
							ganancia += cliente.getConsumo() * 30;
						break;
				}
			}
		}
		
		if (ganancia != gananciaClientes) {
			System.out.println("ganancia de clientes mal calculado ("+ganancia+") ("+gananciaClientes+")");
		}*/
		
		return gananciaClientes;
	}
	
	private double gananciaCliente(Cliente cliente) {
		double ganancia = 0;
		Central central = clientes.get(cliente);
		if (central != null) {
			switch (cliente.getTipo()) {
				case Cliente.CLIENTEG:
					if (cliente.getContrato() == Cliente.GARANTIZADO) 
						ganancia += cliente.getConsumo() * 60;
					else 
						ganancia += cliente.getConsumo() * 50;
					break;
				case Cliente.CLIENTEMG:
					if (cliente.getContrato() == Cliente.GARANTIZADO) 
						ganancia += cliente.getConsumo() * 50;
					else 
						ganancia += cliente.getConsumo() * 40;
					break;
				case Cliente.CLIENTEXG:
					if (cliente.getContrato() == Cliente.GARANTIZADO) 
						ganancia += cliente.getConsumo() * 40;
					else 
						ganancia += cliente.getConsumo() * 30;
					break;
			}
		}
		return ganancia;
	}
	
	/**
	 * Prueba unitaria de la clase
	 * @param args
	 */
	public static void main(String[] args) {
		Estado estadoTest = new Estado();
		Cliente cli = new Cliente(0, 0, 0, 0, 0);
		HashMap<Cliente, Central> cliente = new HashMap<Cliente, Central>();
		cliente.put(cli, new Central(0, 0, 0, 0));
		HashMap<Cliente, Central> cliente2 = new HashMap<Cliente, Central>(cliente);
		cliente2.remove(cli);
		if (cliente.size() == cliente2.size()) {
			System.out.println("Test KO");
		} else {
			System.out.println("Test OK");
		}
	}
	
}
